import os
import sys
import shutil
import urllib.request
import urllib.parse
import urllib.error

from . import suite_category
import framework.report.supported_report as supported_report
from suite.suite_repo import DEFAULT_SUITE
from common.logger import log, Log
from utils.utils import Utils
from common.benchmark_var import BenchAction, StatusCode, ReportType, \
    ConfigSections as Sections, ConfigOptions as Options, GlobalVar, ResultType
from framework.report.json_maker import JsonMaker
from framework.report.pre_report import PreReport
from workload.workloads_manager import WorkloadManager


class Controller:
    def __init__(self, options, machine_info, run_time):
        self.options = options
        self.machine_info = machine_info
        self.run_time = run_time
        self.test_targets = set()
        self.available_suites = {}
        self.available_workloads = {}
        self.action = self.options[Sections.GENERAL][Options.ACTION]
        self.load_suites(self.options.get(Sections.GENERAL).get("mode"))
        self.setup()

    def load_suites(self, mode: str):
        log.info("load mode: {}".format(mode))
        suite = self.create_suite(DEFAULT_SUITE.get(mode))
        self.available_suites[suite.name] = suite
        self.init_available_workloads(suite)
        self.judge_workloads(mode)

    def judge_workloads(self, mode: str): 
        workloads_list = []
        for dic in DEFAULT_SUITE.get(mode)["subsuites"]:
            workloads_list.extend(dic["workloads"])
        for key in self.available_workloads.keys():
            if key not in workloads_list:
                log.error("Unknown test target \"{}\" "
                    "or target has no matching baseline".format(key))
                sys.exit(StatusCode.UNKNOWN_TEST_TARGET)

    def init_available_workloads(self, suite):
        if suite.workloads:
            for workload in suite.workloads:
                self.available_workloads[workload.name] = workload
        if suite.sub_suites:
            for sub_suite in suite.sub_suites:
                self.init_available_workloads(sub_suite)

    def create_suite(self, suite_info: dict, namespace=None, baseline=None):
        """
        create suite obj
        :param suite_info: suite's information
        :param namespace: the parent namespace of this suite
        :param baseline: the baseline info
        :return: suite obj
        """
        SuiteClass = getattr(suite_category, suite_info.get("type"))
        if not baseline and "baseline" in suite_info:
            baseline = suite_info["baseline"]

        sub_suite_objs = []
        sub_suite_list = suite_info.get("subsuites")
        if not sub_suite_list:
            pass
        else:
            for sub_suite_info in sub_suite_list:
                cur_namespace = namespace
                if not cur_namespace:
                    cur_namespace = suite_info.get("name")
                else:
                    cur_namespace = "{}.{}".format(namespace, suite_info.get("name"))

                sub_suite_baseline = None
                if sub_suite_info["name"] in baseline:
                    sub_suite_baseline = baseline.get(sub_suite_info["name"])
                new_suite_obj = self.create_suite(sub_suite_info, cur_namespace, sub_suite_baseline)
                sub_suite_objs.append(new_suite_obj)
                self.available_suites[new_suite_obj.name] = new_suite_obj

        suite_obj = SuiteClass(suite_info.get("name"), sub_suite_objs, namespace, self.options)
        suite_obj.baseline = baseline
        suite_obj.description = suite_info.get("description")
        suite_obj.create_workloads(suite_info.get("workloads"))
        return suite_obj

    def setup(self):
        self.init_target_list()
        GlobalVar.SKIP_FAIL = self.options.get(Sections.GENERAL).get(Options.SKIP_FAIL)

    def init_target_list(self):
        copies = self.options[Sections.GENERAL][Options.COPIES]
        target_list = self.options[Sections.GENERAL][Options.TEST_TARGET]
        if isinstance(target_list, str):
            target_list = target_list.split(',')
        for target in target_list:
            if target in self.available_suites:
                suite = self.available_suites[target]
                if copies > 1 and suite.support_multi_mode:
                    self.test_targets.add(suite)
                elif copies == 1 and suite.support_single_mode:
                    self.test_targets.add(suite)
            elif target in self.available_workloads:
                workload = self.available_workloads[target]
                if copies > 1 and workload.support_multi_mode:
                    self.test_targets.add(workload)
                elif copies == 1 and workload.support_single_mode:
                    self.test_targets.add(workload)
            else:
                if not WorkloadManager.is_registered(target):
                    log.error("Unknown test target \"{}\" ".format(target))
                    sys.exit(StatusCode.UNKNOWN_TEST_TARGET)
                else:
                    log.info("{} is not support in this mode.".format(target))

        if  0 == len(self.test_targets):
            log.error("Not find valid target in  \"{}\" ".format(target_list))
            sys.exit(StatusCode.UNKNOWN_TEST_TARGET)

    def run_bench_suite(self):
        if self.action == BenchAction.BUILD or self.action == BenchAction.STANDARD:
            self.build()

        if self.action == BenchAction.RUN or self.action == BenchAction.STANDARD:
            self.run()
            self.gen_report()

        if self.action == BenchAction.CLEAN:
            self.clean()
            log.info("clean complete")

    def build(self):
        Utils.log_partition("build benchmark")
        for bench_obj in self.test_targets:
            bench_obj.build()

    def run(self):
        Utils.log_partition("run benchmark")
        for bench_obj in self.test_targets:
            bench_obj.run()
            if isinstance(bench_obj, suite_category.Suite):
                bench_obj.calc_score()

    def find_suite_by_wd(self, wd_name):
        for suite_name, suite_info in self.available_suites.items():
            for workload in suite_info.workloads:
                if wd_name == workload.name:
                    return suite_name, suite_info.description
        log.warning("Unable to find suite for workload {}!".format(wd_name))
        return "UnKnown", ""

    def combine_workloads(self, suite_dict, res):
        suite_name, suite_desc = self.find_suite_by_wd(res.get("name"))
        suite_dict.setdefault(suite_name, {})
        if not suite_dict[suite_name].get("desc"):
            suite_dict[suite_name]["desc"] = suite_desc
        suite_dict[suite_name].setdefault("wd_info", []).append(res)

    @staticmethod
    def get_result(result_list, suite_dict):
        for suite_name, suite_info in suite_dict.items():
            tmp = {
                "workloads": suite_info.get("wd_info"),
                "name": suite_name,
                "type": ResultType.Suite,
                "desc": suite_info.get("desc"),
                "score": 0.00,
                "valid": False
            }
            result_list.append(tmp)

    def gen_json(self, result):
        json_maker = JsonMaker(self.options, result, self.machine_info.machine_info, self.run_time)
        return json_maker.make_json_file(), json_maker.json_file

    @staticmethod
    def upload_json(json_data):
        try:
            request_data = bytes(urllib.parse.urlencode(json_data), encoding="utf-8")
            response = urllib.request.urlopen(GlobalVar.URL, request_data, timeout=GlobalVar.TIMEOUT)
        except Exception as err:
            log.error("Upload json fail!, {}".format(err))
        else:
            if response.status == 200:
                log.info("Upload json success.")
            else:
                log.error("Upload json fail!")

    def gen_report(self):
        Utils.log_partition("report")
        suite_dict = {}
        result_list = []
        file_list = [Log().log_path, self.machine_info.sys_file]
        for bench_obj in self.test_targets:
            res = bench_obj.get_result()
            if res.get("type") == ResultType.Suite:
                result_list.append(res)
            else:
                self.combine_workloads(suite_dict, res)
        if suite_dict:
            self.get_result(result_list, suite_dict)

        json_data, json_file = self.gen_json(result_list)
        if json_data:
            file_list.append(json_file)
            if self.options.get(Sections.GENERAL).get(Options.AUTO_UPLOAD):
                self.upload_json(json_data)
            mode = "{}Suite".format(self.options.get(Sections.GENERAL).get(Options.MODE))
            mode_desc = self.available_suites.get(mode).description
            pre_report = PreReport(json_data, self.options, mode_desc)
            report_data = pre_report.get_report_data()
            if report_data:
                report_format_set = set(self.options.get(Sections.GENERAL).get(Options.REPORT))
                if ReportType.ALL in report_format_set:
                    report_format_set.remove(ReportType.ALL)
                    report_format_set.update({ReportType.PDF, ReportType.TXT, ReportType.CSV})
                for report_format in report_format_set:
                    ReportClass = getattr(supported_report, "{}Report".format(report_format.upper()))
                    report_name = json_file.replace("json", report_format.lower())
                    reporter = ReportClass(report_name, report_data)
                    reporter.make_report()
                    file_list.append(report_name)
        self.move_res_to_output(file_list, json_file)

    def move_res_to_output(self, file_list, json_file):
        json_file = json_file.split("/")[-1]
        new_log_name = json_file.replace("json", "log")

        output_dir = self.options.get(Sections.GENERAL).get(Options.OUTPUT_DIR)
        result_dir = os.path.join(output_dir, json_file.split(".")[0])
        os.makedirs(result_dir)
        for file in file_list:
            if os.path.exists(file):
                shutil.move(file, os.path.join(result_dir, new_log_name) if file.endswith("log") else result_dir)
        log.info("Result dir is %s" % result_dir)

    def clean(self):
        Utils.log_partition("clean benchmark")
        for bench_obj in self.test_targets:
            bench_obj.clean()

    def calc_score(self):
        for bench_obj in self.test_targets:
            pass

    def execute_benchmark(self):
        log.debug("execute_benchmark")
        self.run_bench_suite()
